Creación de imágenes CaaS

​​​​Normas

Información general

Icono normas
Tipo de recurso
Normas
Etiquetas

 

 

 

Ámbito de aplicación de la norma

La Junta de Andalucía tiene como objetivo estratégico el despliegue de sus sistemas bajo un modelo de ejecución en nube híbrida, denominada Nube Corporativa de la Junta de Andalucía (NCJA), que proporcionará una infraestructura segura y escalable para maximizar los beneficios del modelo en nube: escalabilidad, flexibilidad y menores costes.

Por lo tanto, para facilitar el despliegue en nube, a todos los desarrollos de nuevas aplicaciones y nuevos sistemas de información que se creen en la Agencia Digital de Andalucía, se le debe exigir las siguientes normas para creación de imágenes para entornos CaaS.

 

Descripción de la norma

OCI (Open Container Initiative) es una iniciativa de código abierto que se dedica a desarrollar y promover estándares para contenedores de software. Estos estándares incluyen especificaciones para la estructura de los contenedores, así como para su formato y su comportamiento.

Una imagen OCI es un archivo que contiene todo lo necesario para crear y ejecutar un contenedor. Esto incluye el sistema operativo, las aplicaciones y los datos necesarios para ejecutar esas aplicaciones. Las imágenes OCI se pueden crear, distribuir y ejecutar en cualquier plataforma compatible con el estándar OCI. Una vez creada, se puede utilizar para crear contenedores en cualquier entorno que soporte OCI.

Con el fin de facilitar la creación de imágenes OCI que puedan correr sin complicaciones en la futura plataforma CaaS de la ADA (dentro de la Nube Corporativa de Junta de Andalucia), se facilita la siguiente relación de normas y recomendaciones.

 

Norma-Directriz-obligatoria Elegir una imagen base adecuada

Las imágenes de contenedores están formadas por múltiples capas. Cada instrucción contenida en el archivo de texto plano que define las instrucciones necesarias para crear una imagen, representa una capa de imagen. La instrucción "FROM" en el fichero de configuración para la creación de una imagen, es la primera capa del contenedor (imagen base). Las instrucciones posteriores se aplican sobre dicha imagen base.

Se utilizarán las imágenes base que se aprueben para su ejecución en la Nube Corporativa de la Junta de Andalucía. De no existir, se solicitará su generación o validación (para repositorios oficiales de terceros).

a) En el caso de que se opte por solicitar la generar una nueva imagen, se tendrá en cuenta la definición del software de base compatible con la nube vigente en cada momento.

b) Si se va a utilizar imágenes de terceros, se recomienda comprobar el cumplimiento de estos criterios antes de solicitar su validación:

  • Deben ser publicadas por proveedores de confianza
  • Se actualizan periódicamente con los "patches" más recientes
  • Tienen un fichero de texto plano para configurar la creación de la imagen de código abierto como apoyo
  • No tienen librerías o herramientas innecesarias para el alcance de la aplicación

 

Norma-Directriz-obligatoria Dockerfile

Se debe denominar Dockerfile, al archivo de texto plano que define las instrucciones necesarias para crear una imagen, esto se debe, a que la mayoría de las herramientas y servicios relacionados con contenedores utilizan este término. Por lo tanto, utilizar un nombre de archivo diferente puede complicar la integración con otras herramientas o la colaboración con otras personas que estén familiarizadas con el uso de contenedores.

Es por ello, que nace esta norma para utilizar el nombre de archivo predeterminado de Dockerfile a menos que haya una buena razón para hacerlo de otra manera.

 

Norma-Directriz-obligatoria No usar la caché en fase de construcción

"Cache busting" es una técnica que se utiliza en la construcción de contenedores para invalidar la caché de una imagen de contenedor. La caché de una imagen de contenedor es un mecanismo que permite almacenar en caché los resultados de la construcción de una imagen de contenedor, para que puedan reutilizarse en futuras construcciones de la misma imagen.

La técnica "cache busting" se utiliza cuando se quiere evitar que se utilice la caché de una imagen de contenedor y se quiere forzar que se vuelva a construir la imagen desde cero. Esto se puede hacer por diferentes razones, como, por ejemplo, si se ha realizado un cambio significativo en el Dockerfile o si se quiere asegurar que se utilizan los últimos paquetes y herramientas disponibles.

Para realizar "cache busting" en una imagen de contenedor, se pueden utilizar diferentes técnicas, como por ejemplo cambiar la versión de la imagen de contenedor o agregar un comentario al Dockerfile. También se puede utilizar un sistema de gestión de contenedores que permita invalidar la caché. Herramientas específicas, como docker, tienen la opción “--no-cache”. Es posible que otras herramientas de construcción como buildah o kaniko dispongan de opciones similares.

 

 Norma-Directriz-obligatoriaContenerizar un proceso a la vez

El contenedor y el proceso que contiene, comparten el mismo ciclo de vida, por esto, un contenedor solo debería contener un proceso. Por ejemplo, si fuera necesario LAMP (Linux, Apache, MySQL, PHP), en lugar de crear un contenedor con servidor web con el intérprete de PHP y la base de datos, sería necesario relegar cada componente a un contenedor separado. Esto permite administrar y escalar de forma independiente.

 

Norma-Directriz-recomendada Exclusiones de ficheros

Para excluir archivos que no sean relevantes para la compilación (sin reestructurar su repositorio de origen) puede utilizarse un archivo de exclusión de ficheros. Por ejemplo en el caso de Docker, el fichero ".dockerignore".

Este tipo de archivo admite patrones de exclusión similares a los archivos ".gitignore". Esto puede ser útil para reducir el tamaño del contexto enviado al daemon y acelerar la construcción de la imagen.

Aquí hay un ejemplo de un archivo .dockerignore:

# ignore all files in the top-level directory
*
# except for the Dockerfile
!Dockerfile
# ignore all files in the `build` directory build/
# ignore all `.log` files in any directory
*.log

En este ejemplo, se ignorarían todos los archivos en el directorio principal, excepto el archivo Dockerfile. También se ignoraría el directorio build y todos los archivos con la extensión .log en cualquier directorio.

 

Norma-Directriz-recomendada Multistage

Un build multietapa es una característica que permite crear imágenes de contenedores en diferentes etapas, utilizando diferentes herramientas y recursos en cada una de ellas. Esto permite optimizar el tamaño y la eficiencia de las imágenes resultantes.

Ejemplo (para este caso concreto, se ha utilizado Docker):

# Etapa 1: se utiliza una imagen de node como base
FROM node:alpine as builder \
# Se copian los archivos de la aplicación al contenedor \
COPY . . \
Se instalan las dependencias de la aplicación RUN npm install \
# Se compila la aplicación \
RUN npm run build

# Etapa 2: se utiliza una imagen de nginx como base
FROM nginx:alpine \
# Se copian los archivos compilados de la primera etapa al contenedor \
COPY --from=builder /app/build /usr/share/nginx/html \
# Se expone el puerto 80 del contenedor \
class="MsoNormal" style="line-height: normal; background: white;">EXPOSE 80 \
# Se ejecuta nginx al iniciar el contenedor \
CMD ["nginx", "-g", "daemon off;"]

En este ejemplo se utilizan dos etapas. En la primera etapa, se utiliza una imagen de node como base y se compila una aplicación de node. En la segunda etapa, se utiliza una imagen de nginx como base y se copian los archivos compilados de la primera etapa en el contenedor de nginx.

Como se ha comentado anteriormente, la ventaja de utilizar un build multietapa en este caso es que se reduce el tamaño de la imagen final, ya que solo se incluyen los archivos necesarios para ejecutar la aplicación en el contenedor de nginx, eliminando todos los recursos innecesarios utilizados en la primera etapa de compilación.

 

Norma-Directriz-recomendada Ordenación argumentos multi-line

Siempre que sea posible, se debe facilitar los cambios posteriores ordenando los argumentos de varias líneas alfanuméricamente. Esto ayuda a evitar la duplicación de paquetes y haga que la lista sea mucho más fácil de actualizar. Esto también hace que las PRs (Pull Requests) sean mucho más fáciles de leer y revisar. Agregar un espacio antes de una barra invertida (\) también ayuda.

Ejemplo:

RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion \
&& rm -rf /var/lib/apt/lists/*

 

Norma-Directriz-obligatoria Evitar el uso de "root"

No se debe de utilizar el usuario "root" para evitar problemas de escalado de privilegios.

En general, se considera una mala práctica utilizar la cuenta de usuario "root" en un contenedor. La razón es que la cuenta de usuario "root" tiene privilegios completos en el sistema y, si un atacante consigue acceder a un contenedor que utiliza "root", podría tener acceso a todos los recursos del host y del sistema en general.

En su lugar, se recomienda utilizar una cuenta de usuario con permisos más limitados en un contenedor. Esto reduce el riesgo de que un atacante tenga acceso a recursos importantes en caso de que consiga acceder al contenedor. También es importante utilizar un sistema de gestión de contenedores que permita establecer y controlar los permisos de las cuentas de usuario en los contenedores.

 

Norma-Directriz-recomendada No instalar paquetes innecesarios

Para reducir la complejidad del entorno en cuanto a tamaño, dependencias y número de compilaciones, se debe evitar instalar dependencias extra a no ser que sean imprescindibles. Por ejemplo, no sería necesario mantener un editor de texto en una imagen para un servidor de bases de datos.

Es recomendable instalar solo los paquetes necesarios en un contenedor. La idea detrás de los contenedores es crear entornos ligeros y portátiles que se puedan desplegar y mover fácilmente. Instalar solo los paquetes necesarios en un contenedor ayuda a optimizar su tamaño y aumenta su eficiencia.

Además, tener un contenedor con solo los paquetes necesarios reduce el riesgo de que contenga vulnerabilidades o errores de seguridad que puedan ser explotados por un atacante. Por lo tanto, es importante ser cuidadoso al seleccionar y configurar los paquetes que se instalan en un contenedor.

 

Norma-Directriz-recomendada Eliminar paquetes innecesarios tras su uso

Es recomendable eliminar paquetes innecesarios en un contenedor después de su uso. Al igual que es importante instalar solo los paquetes necesarios en un contenedor, también es importante eliminar los paquetes que no vayan a utilizarse pero que se hayan instalado porque eran necesarios para hacer una función específica durante la generación de la imagen. Esto ayuda a mantener el contenedor limpio y optimizado, y reduce el riesgo de que contenga vulnerabilidades o errores de seguridad que puedan ser explotados por un atacante.

Para eliminar paquetes innecesarios en un contenedor, se puede utilizar comandos como apt-get remove o apt-get purge en un contenedor basado en Ubuntu, o dnf erase en un contenedor basado en Fedora, por ejemplo. También se pueden utilizar herramientas específicas para administrar paquetes en diferentes sistemas operativos y distribuciones de Linux.

Ejemplo (para este caso concreto, se ha utilizado Docker):

RUN apt-get update && \
apt-get install -y netcat && \
ACCIÓN NECESARIA && \
apt-get remove -y netcat && \

 

Norma-Directriz-recomendada Usar variables de entorno

Cuando se trabaja con contenedores, hay algunas recomendaciones que se deben tener en cuenta en relación con las variables de entorno:

  • Evitar utilizar variables de entorno sensibles en un contenedor. Las variables de entorno sensibles incluyen contraseñas, claves de autenticación y cualquier otro tipo de información confidencial. Si un atacante consigue acceder a un contenedor que contiene variables de entorno sensibles, podría utilizarlas para acceder a recursos protegidos.
  • Utilizar variables de entorno en lugar de hardcodear valores en el código de la aplicación. Esto permite modificar los valores de las variables de entorno sin tener que modificar el código de la aplicación, lo que facilita la gestión, el cambio de configuración de la aplicación y por ende, la portabilidad entre los distintos entornos.
  • Configurar las variables de entorno en tiempo de ejecución en lugar de incluirlas en la imagen del contenedor. Esto permite actualizar y modificar las variables de entorno de manera dinámica, sin tener que volver a construir la imagen del contenedor. Los orquestadores como Kubernetes, incorporan características para permitir la inyección de dichas variables en tiempo de ejecución.
  • Utilizar un sistema de orquestación de contenedores (como Kubernetes) que permita gestionar y controlar las variables de entorno de manera eficiente. Esto facilitará la administración y el seguimiento de las variables de entorno en diferentes contenedores y entornos.

Se recomienda usar variables de entorno, en lugar de incrustar valores en el archivo que contiene las instrucciones necesarias para crear una imagen. Esto permite mantener todos los cambios en un mismo sitio, siendo visible en dicho fichero de forma más eficiente y ordenada.

Hay que tener en cuenta que declarar variables de entorno con ENV, crea una capa intermedia que persiste incluso borrando dichas variables en un paso posterior, por lo que estas variables estarán en el contenedor. Para evitar esta situación se debería declarar, usar y eliminar en la misma línea (SET, USE, UNSET)

 

Norma-Directriz-obligatoria Etiquetar imágenes

Las imágenes de contenedores se identifican con nombre y etiqueta.

Ejemplo:
Para la imagen "nginx:1.17.2", "nginx" es el nombre de la imagen y "1.17.2" es la etiqueta.

Aunque técnicamente es posible, no se debe reutilizar etiquetas. Asegurar que las etiquetas son inalterables y unívocas durante todo el ciclo de vida de la imagen.

Se recomienda la siguiente estrategia de etiquetado:
Versionado semántico: Usar un número de versión con el formato X.Y.Z (Major.Minor.Patch)

 

Recomendaciones sobre el archivo de texto plano que define las instrucciones necesarias para crear una imagen

Estas recomendaciones están pensadas para ayudar a crear un fichero eficiente y fácil de mantener.

 

Norma-Directriz-recomendada LABEL

Pueden agregarse etiquetas a la imagen OCI compatible para ayudar a organizar las imágenes por proyecto, para ayudar en la automatización, etc.

La sintaxis de la instrucción LABEL es la siguiente:

LABEL <key>=<value> <key>=<value> <key>=<value> ...

Donde cada par clave-valor especifica una etiqueta que se asignará a la imagen de contenedor. Los siguientes ejemplos muestran los diferentes formatos aceptados:

  • LABEL com.example.version="0.0.1-beta"
  • LABEL vendor1="ACME Incorporated"
  • LABEL vendor2=ZENITH\ Incorporated
  • LABEL com.example.release-date="2015-02-12"
  • LABEL com.example.version.is-production=""

Una imagen puede tener más de una etiqueta. Antes se recomendaba combinar todas las etiquetas en una sola instrucción LABEL para evitar la creación de capas adicionales. Esto ya no es necesario, pero la combinación de etiquetas sigue siendo compatible:

  • LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

 

Norma-Directriz-recomendada RUN

La instrucción RUN se utiliza para realizar diferentes tareas, como instalar paquetes, configurar el contenedor y ejecutar scripts. La instrucción RUN se ejecuta en el orden en que aparece en el Dockerfile, por lo que es importante colocarla en el orden adecuado para que los comandos se ejecuten correctamente.

Algunas recomendaciones:

  • Utilizar comandos simples y precisos en la instrucción RUN. Los comandos deben ser fáciles de entender y deberían proporcionar información clara y concisa sobre lo que hacen.
  • Dividir las instrucciones RUN largas o complejas en varias instrucciones RUN más simples y fáciles de entender. Esto ayuda a mejorar la legibilidad y claridad del Dockerfile.
  • Utilizar la instrucción RUN para instalar paquetes solo cuando sea necesario. Evitar utilizar la instrucción RUN para instalar paquetes que no se utilizan en la aplicación en contenedores, ya que esto puede aumentar el tamaño y la complejidad de la imagen de contenedor.
  • Utilizar la instrucción RUN para ejecutar scripts y comandos que configuren el contenedor. La instrucción RUN permite personalizar el contenedor según las necesidades y requisitos.
  • Utilizar la instrucción RUN para ejecutar tareas de limpieza y mantenimiento en el contenedor. Por ejemplo, se puede utilizar la instrucción RUN para eliminar paquetes innecesarios o archivos temporales que se hayan generado durante la construcción de la imagen.
  • Utilizar comentarios para documentar las instrucciones RUN y proporcionar información útil y relevante sobre cada una de ellas.

En general, es recomendable minimizar el uso de comandos RUN en un Dockerfile para reducir el número de capas en la imagen de contenedor. Cuantas menos capas tenga una imagen de contenedor, más ligera y eficiente será.

Las capas son uno de los conceptos fundamentales de los contenedores. Cada capa contiene los cambios realizados en el contenedor por un comando RUN, así como los archivos y directorios que se hayan agregado al contenedor con instrucciones como ADD o COPY. Las capas se almacenan en el sistema de ficheros.

  • Cuantos más comandos RUN se utilicen en un Dockerfile, más capas se crearán en la imagen de contenedor. Esto puede aumentar el tamaño y la complejidad de la imagen de contenedor, lo que dificulta su gestión y su movilidad.
  • Cuando se utiliza una imagen de contenedor, las capas se cargan y se combinan para crear el entorno del contenedor. Cuantas más capas tenga una imagen de contenedor, más tiempo tardará en cargarse y combinarse para crear el entorno del contenedor. Esto puede ralentizar el despliegue y el arranque de los contenedores.
  • Cuando se utiliza una imagen de contenedor, las capas se cargan y se combinan para crear el entorno del contenedor. Si hay muchas capas en una imagen de contenedor, puede ser difícil entender y seguir la secuencia de comandos que se han ejecutado en el contenedor durante la construcción de la imagen. Esto puede dificultar el mantenimiento y la depuración del Dockerfile.

Por estas razones, es recomendable minimizar el uso de comandos RUN en un Dockerfile para reducir el número de capas en la imagen de contenedor. Para lograrlo, se pueden utilizar varias técnicas, como la anteriormente comentada de combinar varios comandos RUN en uno solo, utilizar instrucciones como ENV para configurar el contenedor en lugar de comandos RUN, o utilizar instrucciones como ONBUILD para ejecutar comandos en contenedores hijo en lugar de en el contenedor padre o la utilización de build multistages.

Es importante tener en cuenta que no todos los comandos que se utilizan en un Dockerfile crean una capa. Algunos comandos, como por ejemplo ENV o LABEL, no crean una capa, sino que simplemente configuran el contenedor.

 

Norma-Directriz-recomendada Sobre el uso de ADD o COPY

La instrucción ADD y la instrucción COPY son dos instrucciones que se pueden utilizar en un Dockerfile para copiar archivos o directorios en un contenedor durante la construcción de la imagen de contenedor.

Ambas instrucciones tienen una sintaxis similar, pero existen algunas diferencias importantes entre ellas. A continuación, se describen algunas de las principales diferencias entre ADD y COPY:

  • La instrucción ADD permite copiar archivos y directorios desde una URL. Esto significa que se puede utilizar la instrucción ADD para descargar archivos y directorios directamente desde una ubicación en red y copiarlos en el contenedor. La instrucción COPY no permite esto, solo permite copiar archivos y directorios desde el sistema de archivos local.
  • La instrucción ADD permite descomprimir archivos comprimidos (zip, tar, gzip, etc.) automáticamente. Esto significa que se puede utilizar la instrucción ADD para copiar archivos comprimidos en el contenedor y descomprimirlos automáticamente durante la construcción de la imagen de contenedor. La instrucción COPY no permite esto, solo permite copiar archivos comprimidos tal como están.

En consecuencia, es mejor uso para ADD es la extracción automática de archivos .tar locales en la imagen, como: “ADD rootfs.tar.xz /”.

Si existen varios pasos en el fichero de configuración para la creación de una imagen, que utilizan diferentes archivos del contexto, es recomendable utilizar COPY individualmente sobre cada uno de ellos, en lugar de todos a la vez. Esto garantiza que la memoria caché de compilación de cada paso solo se invalide (lo que obliga a volver a ejecutar el paso) si cambian los archivos específicamente requeridos. Por ejemplo:

  • COPY requirements.txt /tmp/
  • RUN pip install --requirement /tmp/requirements.txt
  • COPY . /tmp/

Esto da como resultado menos invalidaciones de caché para el paso RUN, que si se coloca “COPY. /tmp/” antes.

Debido a que el tamaño de la imagen es importante, se desaconseja el uso de ADD para traer paquetes de URL remotas; sería más recomendable utilizar “curl” o “wget” en su lugar.

De esa manera, pueden eliminarse los archivos que ya no se necesiten después de haberlos extraído y no es necesario agregar otra capa en la imagen. Por ejemplo, debe evitarse este tipo de instrucciones:

  • ADD https://example.com/big.tar.xz /usr/src/things/
  • RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
  • RUN make -C /usr/src/things all

Y se deben de sustituir por:

  • RUN mkdir -p /usr/src/things \
    • && curl -SL https://example.com/big.tar.xz \
    • | tar -xJC /usr/src/things \
    • && make -C /usr/src/things all

Para otros elementos (archivos, directorios) que no requieren la capacidad de extracción automática de los .tar de la instrucción ADD, siempre debe utilizarse COPY en su lugar.

 

Versiones

Fecha Nombre de la versión
Creación de imágenes CaaS - v01r00